/**************************************************************************************************
* Title: SimulationManager.cpp
* Authors: Gael Huber, Erin Mooney
* Description: Manages the actual game
**************************************************************************************************/
#include "SimulationManager.h"

// Singleton instance
template<> SimulationManager* Singleton<SimulationManager>::singleton = 0;

// Constructor
SimulationManager::SimulationManager(void) : numEntities(0) {
}	

// Destructor
SimulationManager::~SimulationManager(void) {
}

// Initialize
void SimulationManager::startUp(void) {
	// random seed
	srand(time(0));

	// create the input manager
	inputManager = new InputManager();
	
	// create the render manager
	renderManager = new RenderManager();
	renderManager->startUp();

	// create physics manager
	physicsManager = new PhysicsManager();

	// Create the matrix reader
	matrixReader = new MatrixReader();

	// Create a bezier surface
	surface = new BezierSurface(Vector3(0, 0, -20.0f), 10.0f, 10.0f, 100);
	surface->P = matrixReader->loadMatrix("bezier.mat");
	surface->buildSurface();

	for(unsigned int i = 0; i < surface->resolution - 1; i++) {
		for(unsigned int j = 0; j < surface->resolution - 1; j++) {
			// Assemble the polygons as squares
			Mesh* mesh = new Mesh(CUBE);
			// Find the "center" of the square
			// Rotate the vertices
			Quaternion q = Quaternion(PI / 4, Vector3(1.0f, 0, 0));

			Vector3 horizontal = (surface->vertexMap[i][j+1] - surface->vertexMap[i][j]) / 2.0f;
			Vector3 vertical = (surface->vertexMap[i+1][j] - surface->vertexMap[i][j]) / 2.0f;
			Vector3 center = surface->vertexMap[i][j] + horizontal + vertical;

			// Add the points to the mesh and rotate them so they display more pleasingly to the eye in the world
			Vector3 pos = surface->vertexMap[i][j+1];
			pos.rotate(q);
			mesh->vertices.push_back(pos - center);
			pos = surface->vertexMap[i][j];
			pos.rotate(q);
			mesh->vertices.push_back(pos - center);
			pos = surface->vertexMap[i+1][j];
			pos.rotate(q);
			mesh->vertices.push_back(pos - center);
			pos = surface->vertexMap[i+1][j+1];
			pos.rotate(q);
			mesh->vertices.push_back(pos - center);
			mesh->colors.push_back(Vector3(1.0f, 0, 0));
			mesh->colors.push_back(Vector3(1.0f, 1.0f, 0));
			mesh->colors.push_back(Vector3(0, 1.0f, 0));
			mesh->colors.push_back(Vector3(0, 0, 1.0f));

			// Screen coordinates
			mesh->screenCoords = center + surface->center;

			renderManager->addToScene(mesh);
		}
	}

	// create a sphere
	Entity* sphere;

	// create its mesh
	Mesh* mesh = new Mesh(SPHERE);
	RigidBody* body = new RigidBody(mesh->rotationAxis, mesh->halfDimensions);
	body->axisOfRotation = Vector3(1.0f,1.0f,1.0f);
	// random angle
	float angle = 0;
	while(angle == 0) {
		angle = (float)(rand() % 10 - 5); // / 5.0f;
		angle *= PI / 180.0f;
		body->angle = angle;
	}
	// random velocity
	float x, y, z;
	x = y = z = 0;
	while(x == 0 && y == 0 && z == 0) {
		x = (float)(rand() % 10 - 5) / 100.0f;
		y = (float)(rand() % 10 - 5) / 100.0f;
		z = (float)(rand() % 10 - 5) / 100.0f;
	}
	body->velocity = Vector3(x, y, z);

	// set the object's position/screen coordinates based on the vector passed in
	mesh->screenCoords = Vector3(0, 0.5f, -10.0f);
	body->position = Vector3(0, 0.5f, -10.0f);

	// Create the entity
	sphere = new Entity(mesh, body);
	
	// Add the mesh to the list of objects to render
	renderManager->addToScene(mesh);

	// Add the body to the list of rigid bodies to update
	physicsManager->addToWorld(body);

	// Add to entity list
	entityList.push_back(sphere);
}

// Shutdown
void SimulationManager::shutDown(void) {
}

/**************************************************************************************************
* Get the singleton instance
**************************************************************************************************/
SimulationManager& SimulationManager::getSingleton(void) { 
	assert(singleton);
	return *singleton;
}

/**************************************************************************************************
* Get the singleton pointer
**************************************************************************************************/
SimulationManager* SimulationManager::getSingletonPtr(void) {
	return singleton;
}

// Doubles the rotation speed of all entities in the world
void SimulationManager::increaseRotationSpeed(void) {
	std::vector<Entity*>::iterator begin = entityList.begin();
	std::vector<Entity*>::iterator end = entityList.end();

	// Iterate through the list change the rotation angle
	for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
		(*itr)->getBody()->angle += 0.005f;
	}
}

// Halves the rotation speed of all entites in the world
void SimulationManager::decreaseRotationSpeed(void) {
	std::vector<Entity*>::iterator begin = entityList.begin();
	std::vector<Entity*>::iterator end = entityList.end();

	// Iterate through the list change the rotation angle
	for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
		(*itr)->getBody()->angle -= 0.005f;
	}
}
	
// Doubles the velocity of all entities in the world
void SimulationManager::increaseVelocity(void) {
	std::vector<Entity*>::iterator begin = entityList.begin();
	std::vector<Entity*>::iterator end = entityList.end();

	// Iterate through the list change the velocity
	for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
		(*itr)->getBody()->velocity.x += 0.005f;
		(*itr)->getBody()->velocity.y += 0.005f;
		(*itr)->getBody()->velocity.z += 0.005f;
	}
}
	
// Half the velocity of all entities in the world
void SimulationManager::decreaseVelocity(void) {
	std::vector<Entity*>::iterator begin = entityList.begin();
	std::vector<Entity*>::iterator end = entityList.end();

	// Iterate through the list change the velocity
	for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
		(*itr)->getBody()->velocity.x -= 0.005f;
		(*itr)->getBody()->velocity.y -= 0.005f;
		(*itr)->getBody()->velocity.z -= 0.005f;
	}
}

// Update
void SimulationManager::gameLoop(void) {
	MSG msg;	// Windows message
	BOOL done = FALSE;	// Bool variable to exit loop

	while( !done ) {
		// Is there a message waiting?
		if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
			// Have we received a quit message?
			if( msg.message == WM_QUIT ) {
				done = TRUE;	// If so, we want to quit
			} else {
				// Otherwise deal with Windows messages
				TranslateMessage(&msg);	// Translate the message
				DispatchMessage(&msg);	// Dispatch the message
			}
		} else { // If there is no message
			// handle input
			inputManager->handleInput();

			// Run if the game is not paused
			if(!getGamePaused()) {
				// Update physics
				physicsManager->update();

				// Post-physics update
				// Define start and end of vector
				std::vector<Entity*>::iterator begin = entityList.begin();
				std::vector<Entity*>::iterator end = entityList.end();

				// Iterate through the list and update each object
				for(std::vector<Entity*>::iterator itr = begin; itr != end; ++itr) {
					// Apply update
					(*itr)->postPhysicsUpdate();
					// Test height
					adjustHeight((*itr));
				}

				
			}

			// Draw the scene
			renderManager->renderScene();
		}
	}
}

/**************************************************************************************************
* Adjust the height of the ball
**************************************************************************************************/
void SimulationManager::adjustHeight(Entity* ent) {
	// The grid indices for the sphere
	int indexX = 0;
	int indexZ = 0;
	bool updateX = false;
	bool updateZ = false;

	// Find out the position in terms of the world coordinates, since it starts in relative coordinates
	Vector3 pos = ent->getBody()->position - surface->center;
	Quaternion q = Quaternion(-PI / 4, Vector3(1.0f, 0, 0));
	pos.rotate(q);
	pos += surface->center;
	
	// ( x - minX )
	// ( -------- ) * resolution
	// (  width   )
	float halfWidth = surface->width / 2.0f;	// halfwidth
	float minX = surface->center.x - (surface->width / 2.0f);	// minX
	float diffX = pos.x - minX;	// x - minX
	float percentX = diffX / surface->width;	// divide by width
	indexX = (unsigned int) std::floorf(percentX * surface->resolution);	// *resolution

	float halfHeight = surface->height / 2.0f;	// halfheight
	float minZ = surface->center.z - (surface->height / 2.0f);	// minZ
	float diffZ = pos.z - minZ;	// z - minZ
	float percentZ = diffZ / surface->height;	// divide by height
	indexZ = (unsigned int) std::floorf(percentZ * surface->resolution);	// *resolution

	// Wrap around
	if(indexX >= (int) surface->resolution) {
		indexX = 0;
		updateX = true;
	}
	else if(indexX < 0) {
		indexX = (int) surface->resolution - 1;
		updateX = true;
	}

	if(indexZ >= (int) surface->resolution) {
		indexZ = 0;
		updateZ = true;
	}
	else if(indexZ < 0) {
		indexZ = (int) surface->resolution - 1;
		updateZ = true;
	}

	// Rotate back into relative coordinate space
	q = Quaternion(PI / 4, Vector3(1.0f, 0, 0));
	Vector3 position = surface->vertexMap[indexX][indexZ];
	position.rotate(q);
	position += surface->center;

	// Update positions as necessary
	ent->getBody()->position.y = position.y;

	if(updateX)
		ent->getBody()->position.x = position.x;

	if(updateZ)
		ent->getBody()->position.z = position.z;
}

// getter for the number of entities in the world
int SimulationManager::getNumEntities(void) { return numEntities; }